#include "engine.h"
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <iostream>
#include <sys/time.h>
#include <memory.h>
using namespace std;

    int **A;

    inline long long gettimeus()
    {
        struct timeval tv;

        gettimeofday( &tv, NULL );
        return (long long) tv.tv_sec * 1000000LL + (long long) tv.tv_usec;
    }

    int Engine::PRNG(int &seed, int a, int b, int m)
    {
      return (a * seed + b) % m;
    }

    Engine::Engine(int M, int N, int seed0, int a, int b, int m, double start){
        this->M=M;
        this->N=N;
        this->start=start;
        A=(int**)malloc(M*sizeof(int*));
        for(int i=0; i< M; i++){
            A[i]=(int*)malloc(N*sizeof(int*));
        }

        int i, j;
        int sum = 0;
        int seed = seed0;

        for (i = 0; i<M;i++) {
            for (j = 0; j<N;j++) {
                seed = PRNG(seed, a, b, m);
                A[i][j] = seed;

                sum = sum + A[i][j];
            }

        }

        int mean = ((float)sum / (M*N)) + 0.5;

        for (i = 0; i<M;i++) {
            for (j = 0; j<N;j++) {
                A[i][j] = A[i][j] - mean;
            }
        }

    }

    Engine::~Engine(){

        for (int i = 0; i < M; i++) {
          free(A[i]);
        }
        free(A);
    }

    void Engine::printToScreen(){
        printf("Matrix A:\n");
        for (int i = 0; i<M;i++) {
            for (int j = 0; j<N;j++) {
                printf("%3d ",A[i][j]);
            }
            printf("\n");
        }
    }

    void Engine::printToScreen(int **matrix, int a,int b){
        printf("Some Matrix:\n");

        for (int i = 0; i<a;i++) {
            for (int j = 0; j<b;j++) {
                printf("%3d ",matrix[i][j]);
            }
            printf("\n");
        }
    }


    //возвращает элемент матрицы B
    int recursiveSumm(int **A, int **B, int i, int j){
        //printf("i=%d j=%d\n",i,j);
        if((i<0)||(j<0))
            return 0;
        if((i==0)||(j==0)){
            //printf("B[%d][%d]=%d\n",i,j,B[i][j]);
            return B[i][j];
        }
        B[i][j]=-recursiveSumm(A,B,i-1,j-1)+recursiveSumm(A,B,i,j-1)+recursiveSumm(A,B,i-1,j)+A[i][j];
        //printf("B[%d][%d]=%d\n",i,j,B[i][j]);
        return B[i][j];
    }

    int getSumm(int **B, int i1, int j1, int i2, int j2){
        int summ=B[i2][j2];

        if(i1>0)
            summ-=B[i1-1][j2];
        if(j1>0)
            summ-=B[i2][j1-1];
        if((i1>0)&&(j1>0))
            summ+=B[i1-1][j1-1];

        return summ;
    }


    void Engine::solve1(){
        int i,j;

        //инициализация матрицы
        int **B;

        B=(int**)malloc(M*sizeof(int*));
        for(i=0; i< M; i++){
            B[i]=(int*)malloc(N*sizeof(int*));
        }

        //заполняем крайние ячейки
        B[0][0]=A[0][0];

        //#pragma omp parallel for
        for(i=1;i<M;i++)
            B[i][0]=B[i-1][0]+A[i][0];
        //#pragma omp parallel for
        for(j=1;j<N;j++)
            B[0][j]=B[0][j-1]+A[0][j];

        #ifdef _OPENMP
            double time=omp_get_wtime();
#else
        double time=gettimeus();
        #endif
        recursiveSumm(A,B,M-1,N-1);
        printf("matrix B buikded in %.16g\n", time - start);

        printToScreen(B,M,N);


        int max_value=B[0][0];
        int answer_i, answer_j, answer_k, answer_l;
        answer_i=0;
        answer_j=0;
        answer_k=0;
        answer_l=0;

#ifdef _OPENMP
    time=omp_get_wtime();
#else
    time=gettimeus();
#endif

        int k=0;
        int l=0;
        int summ=0;
        int m=M;
        int n=N;

        //omp_set_num_threads(4);
#ifdef _OPENMP
    int max_threads=omp_get_max_threads();
    printf("max threads %d\n",max_threads);
    int **solution;
    solution=(int**)malloc(max_threads*sizeof(int*));
            for(int i=0; i< max_threads; i++){
                solution[i]=(int*)malloc(5*sizeof(int*));
            }

            for (i = 0; i < max_threads; i++){
                for(j = 0; j< 5; j++)   {
                    solution[i][j]=B[0][0];
                }
            }


        //int **matrix;
        //memcpy(matrix,B,sizeof(int)*m*n);
        #pragma omp parallel for shared (B,solution) private(summ)/*private(k,l,summ)*/ firstprivate(max_value,k,l,j,m,n) // lastprivate(max_value)

#else
//foudroyer eecom
#endif
        for(i=0;i<m;i++){
            //#pragma omp private (j)
            for(j=0;j<n;j++){
                //#pragma omp private(k,i)
                for(k=i;k<m;k++){
                    //#pragma omp parallel private(l,j)
                    for(l=j;l<n;l++){/*
                        int current_value=getSumm(B,i,j,k,l);
                        /*#pragma omp flush(max_value)

                        */
                        if((i<=k)&&(j<=l)){
                            summ=B[k][l];

                            if(i>0)
                                summ-=B[i-1][l];
                            if(j>0)
                                summ-=B[k][j-1];
                            if((i>0)&&(j>0))
                                summ+=B[i-1][j-1];
                            /*
#ifdef _OPENMP
                            if((i==0)&&(j==0)&&(k==3)&&(l==2)) //./a.out 6 6 10 3 4 19
                                printf("N thread=%d i=%d j=%d k=%d l=%d S=%d max_value=%d\n",omp_get_thread_num(),i,j,k,l,summ,max_value);

#endif
*/
                            //#pragma omp flush(max_value)
                                if(summ>max_value){

                                        max_value=summ;
                                        #ifdef _OPENMP
                                        solution[omp_get_thread_num()][0]=max_value;
                                        solution[omp_get_thread_num()][1]=i;
                                        solution[omp_get_thread_num()][2]=j;
                                        solution[omp_get_thread_num()][3]=k;
                                        solution[omp_get_thread_num()][4]=l;
                                        //printf("thread=%d i=%d j=%d k=%d l=%d S=%d max_value=%d\n",omp_get_thread_num(),i,j,k,l,summ,max_value);
                                        #else
                                        answer_i=i;
                                        answer_j=j;
                                        answer_k=k;
                                        answer_l=l;
                                        printf("i=%d j=%d k=%d l=%d S=%d max_value=%d\n",i,j,k,l,summ,max_value);
                                        #endif

                                }
                        }
                    }
                }
            }
        }

        printf("solution finded in %.16g\n", time - start);





        #ifdef _OPENMP
        for (i = 0; i < max_threads; i++){

                printf("thread= %d; sum=%d; i=%d; j=%d; k=%d; l=%d\n", i, solution[i][0], solution[i][1], solution[i][2], solution[i][3], solution[i][4]);

        }
        for (i = 0; i < max_threads; i++){
          free(solution[i]);
        }

        free(solution);
#else
        printf("%d %d %d %d ; sum=%d\n",answer_i,answer_j,answer_k,answer_l,getSumm(B,answer_i,answer_j,answer_k,answer_l));
        #endif

        //освобождение памяти
        for (i = 0; i < M; i++){
          free(B[i]);
        }

        free(B);
    }
